package com.yy.app.config;

import com.yy.app.config.custom.CookieAuthorizationRequestRepository;
import com.yy.app.config.custom.CustomAuthorizationCodeTokenResponseClient;
import com.yy.app.config.custom.RetryableTokenResponseClient;
import com.yy.app.config.filter.LoggingTokenResponseClient;
import com.yy.app.config.filter.StateValidationFilter;
import com.yy.app.util.OAuth2SafeUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;

import java.util.Map;

/**
 * @Description 客户端-安全配置
 * @Date 2025/10/10 上午10:19
 * @Author yanglin
 **/
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http, CookieAuthorizationRequestRepository cookieAuthorizationRequestRepository, StateValidationFilter stateValidationFilter) throws Exception {
        http
                .addFilterBefore(stateValidationFilter, OAuth2AuthorizationRequestRedirectFilter.class)
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/", "/login", "/error", "/css/**", "/js/**", "/images/**").permitAll()
                        .anyRequest().authenticated()
                )
                .exceptionHandling(exceptions -> exceptions
                        .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/oauth2/authorization/client1"))
                )
                .oauth2Login(oauth2 -> oauth2
                        .loginPage("/login")
                        .defaultSuccessUrl("/secured", true)
                        .failureUrl("/login?error=f")
                        //.successHandler((request, response, authentication) -> {
                        //    // 保存认证信息到会话
                        //    request.getSession().setAttribute("SPRING_SECURITY_CONTEXT",
                        //            SecurityContextHolder.getContext());
                        //})
                        .tokenEndpoint(token -> token
                                .accessTokenResponseClient(accessTokenResponseClient())
                        )
                        .authorizationEndpoint(authorization -> authorization
                                .baseUri("/oauth2/authorization")
                                // 自定义授权请求仓库
                                .authorizationRequestRepository(cookieAuthorizationRequestRepository)
                        )
                        .redirectionEndpoint(redirection -> redirection
                                .baseUri("/login/oauth2/code/*") // 确保与授权服务器注册的redirect_uri匹配
                        )
                )
                .logout(logout -> logout
                        .logoutSuccessUrl("/")
                        .invalidateHttpSession(true)
                        .clearAuthentication(true)
                        .deleteCookies("JSESSIONID")
                )
                .csrf(csrf -> csrf
                        .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                )
                .sessionManagement(session -> session
                        .sessionFixation().migrateSession()
                        .maximumSessions(1)
                        .maxSessionsPreventsLogin(false)
                );
        ;
        return http.build();
    }


    /**
     * 会话管理配置
     *
     * @return
     */
    @Bean
    public CookieSerializer cookieSerializer() {
        DefaultCookieSerializer serializer = new DefaultCookieSerializer();
        serializer.setCookieName("SESSION");
        serializer.setSameSite("Lax"); // 允许跨站请求
        serializer.setUseHttpOnlyCookie(true);
        serializer.setCookiePath("/");
        return serializer;
    }

    /**
     * CSRF 保护配置
     *
     * @return
     */
    @Bean
    public CsrfTokenRepository csrfTokenRepository() {
        CookieCsrfTokenRepository repository = CookieCsrfTokenRepository.withHttpOnlyFalse();
        repository.setCookiePath("/");
        repository.setCookieName("XSRF-TOKEN");
        return repository;
    }

    @Bean
    public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> customAccessTokenResponseClient() {
        return new RetryableTokenResponseClient(
                new LoggingTokenResponseClient(
                        new CustomAuthorizationCodeTokenResponseClient()
                )
        );
    }

    /**
     * 令牌响应处理
     * @return
     */
    @Bean
    public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient() {
        DefaultAuthorizationCodeTokenResponseClient client = new DefaultAuthorizationCodeTokenResponseClient();

        return request -> {
            OAuth2AccessTokenResponse response = client.getTokenResponse(request);

            // 安全处理 additionalParameters
            Map<String, Object> additionalParameters = OAuth2SafeUtils.safeAdditionalParameters(
                    response.getAdditionalParameters()
            );

            return OAuth2AccessTokenResponse.withResponse(response)
                    .additionalParameters(additionalParameters)
                    .build();
        };
    }

}
